package com.ebarlas.fractal;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.List;

import com.ebarlas.fractal.compile.Interpreter;
import com.ebarlas.fractal.compile.ParseException;
import com.ebarlas.fractal.compile.Parser;
import com.ebarlas.fractal.compile.ParserImpl;
import com.ebarlas.fractal.compile.ScanException;
import com.ebarlas.fractal.compile.StackCommand;
import com.ebarlas.fractal.compile.Token;
import com.ebarlas.fractal.compile.Variable;
import com.ebarlas.fractal.util.ArrayStack;
import com.ebarlas.fractal.util.Complex;

public class InterpreterFractalService implements FractalService {
    
    @Override
    public BufferedImage createFractalImage(Fractal fractal, int imgWidth, int imgHeight) throws ScanException, ParseException {
        return createFractalImage(parse(fractal), imgWidth, imgHeight);
    }
    
    @Override
    public void createFractalImages(FractalTransition ft, TransitionListener listener, int frames, int imgWidth, int imgHeight) throws ScanException, ParseException {
        createFractalImages(ft, parse(ft), listener, frames, imgWidth, imgHeight); 
    }
    
    private static ParsedFractalTransition parse(FractalTransition ft) throws ScanException, ParseException {
        Variable pointVar = new Variable();
        Variable iterationVar = new Variable();
        Variable recurrenceVar = new Variable();
        Variable frameVar = new Variable();
        Variable framesVar = new Variable();
        
        Parser parser = new ParserImpl(); 
        
        parser.addVariable("f|F", frameVar);
        parser.addVariable("t|t", framesVar);    
        
        StackCommand[] regionX = parse(parser, ft.getRegionX());
        StackCommand[] regionY = parse(parser, ft.getRegionY());
        StackCommand[] regionWidth = parse(parser, ft.getRegionWidth());
        StackCommand[] regionHeight = parse(parser, ft.getRegionHeight());        
        
        parser.addVariable("c|C", pointVar);

        StackCommand[] base = parse(parser, ft.getBase());
        
        parser.addVariable("n|N", iterationVar);
        parser.addVariable("z|Z", recurrenceVar);
        
        StackCommand[] recurrence = parse(parser, ft.getRecurrence());
        StackCommand[] red = parse(parser, ft.getRed());
        StackCommand[] green = parse(parser, ft.getGreen());
        StackCommand[] blue = parse(parser, ft.getBlue());
        StackCommand[] halt = parse(parser, ft.getHalt());

        ParsedFractalTransition pft = new ParsedFractalTransition();
        pft.setRecurrenceVar(recurrenceVar);
        pft.setPointVar(pointVar);
        pft.setIterationVar(iterationVar);
        pft.setFrameVar(frameVar);
        pft.setFramesVar(framesVar);
        pft.setBase(base);
        pft.setRecurrence(recurrence);
        pft.setRed(red);
        pft.setGreen(green);
        pft.setBlue(blue);
        pft.setHalt(halt);
        pft.setRegionX(regionX);
        pft.setRegionY(regionY);
        pft.setRegionWidth(regionWidth);
        pft.setRegionHeight(regionHeight);
        
        return pft;
    }    
    
    private static ParsedFractal parse(Fractal fractal) throws ScanException, ParseException {
        Variable pointVar = new Variable();
        Variable iterationVar = new Variable();
        Variable recurrenceVar = new Variable();
        
        Parser parser = new ParserImpl();        
        parser.addVariable("c|C", pointVar); 
        
        StackCommand[] base = parse(parser, fractal.getBase());
        
        parser.addVariable("n|N", iterationVar);
        parser.addVariable("z|Z", recurrenceVar);
        
        StackCommand[] recurrence = parse(parser, fractal.getRecurrence());
        StackCommand[] red = parse(parser, fractal.getRed());
        StackCommand[] green = parse(parser, fractal.getGreen());
        StackCommand[] blue = parse(parser, fractal.getBlue());
        StackCommand[] halt = parse(parser, fractal.getHalt());
        
        ParsedFractal pf = new ParsedFractal();
        pf.setRecurrenceVar(recurrenceVar);
        pf.setPointVar(pointVar);
        pf.setIterationVar(iterationVar);
        pf.setBase(base);
        pf.setRecurrence(recurrence);
        pf.setRed(red);
        pf.setGreen(green);
        pf.setBlue(blue);
        pf.setHalt(halt);
        pf.setRegionX(fractal.getRegionX());
        pf.setRegionY(fractal.getRegionY());
        pf.setRegionWidth(fractal.getRegionWidth());
        pf.setRegionHeight(fractal.getRegionHeight());
        
        return pf;
    }      
    
    private static StackCommand[] parse(Parser parser, String expr) throws ScanException, ParseException {
        List<Token> tokens = parser.scan(expr);
        List<StackCommand> list = parser.parse(tokens);
        StackCommand[] cmds = new StackCommand[list.size()];
        return list.toArray(cmds);
    }   
    
    private static int maxStack(ParsedFractal pf) {
        int maxStack = Interpreter.maxStack(pf.getBase());
        maxStack = Math.max(maxStack, Interpreter.maxStack(pf.getRecurrence()));
        maxStack = Math.max(maxStack, Interpreter.maxStack(pf.getRed()));
        maxStack = Math.max(maxStack, Interpreter.maxStack(pf.getGreen()));
        maxStack = Math.max(maxStack, Interpreter.maxStack(pf.getBlue()));
        maxStack = Math.max(maxStack, Interpreter.maxStack(pf.getHalt()));
        return maxStack; 
    } 
    
    private static int maxStack(ParsedFractalTransition pft) {
        int maxStack = Interpreter.maxStack(pft.getBase());
        maxStack = Math.max(maxStack, Interpreter.maxStack(pft.getRecurrence()));
        maxStack = Math.max(maxStack, Interpreter.maxStack(pft.getRed()));
        maxStack = Math.max(maxStack, Interpreter.maxStack(pft.getGreen()));
        maxStack = Math.max(maxStack, Interpreter.maxStack(pft.getBlue()));
        maxStack = Math.max(maxStack, Interpreter.maxStack(pft.getHalt()));
        maxStack = Math.max(maxStack, Interpreter.maxStack(pft.getRegionX()));
        maxStack = Math.max(maxStack, Interpreter.maxStack(pft.getRegionY()));
        maxStack = Math.max(maxStack, Interpreter.maxStack(pft.getRegionWidth()));
        maxStack = Math.max(maxStack, Interpreter.maxStack(pft.getRegionHeight()));
        return maxStack; 
    }    
    
    private static Color computeCurrentPoint(ParsedFractal pf) {
        ArrayStack<Complex> stack = new ArrayStack<Complex>(maxStack(pf));
        
        // set iteration to 0
        pf.getIterationVar().setValue(Complex.ZERO);
        
        // compute base
        pf.getRecurrenceVar().setValue(Interpreter.interpret(pf.getBase(), stack));
        
        // iterate while not halted by halt expression
        for(int i=1; !Interpreter.interpret(pf.getHalt(), stack).getBoolean(); i++) {
            // update iteration 
            pf.getIterationVar().setValue(new Complex(i));
                    
            // compute recurrence
            pf.getRecurrenceVar().setValue(Interpreter.interpret(pf.getRecurrence(), stack));
        }         

        // compute rgb
        int r = Math.abs(Interpreter.interpret(pf.getRed(), stack).getInt()) % 256;
        int g = Math.abs(Interpreter.interpret(pf.getGreen(), stack).getInt()) % 256;
        int b = Math.abs(Interpreter.interpret(pf.getBlue(), stack).getInt()) % 256;

        // return result  
        return new Color(r, g, b);        
    }  
    
    private static Color computeCurrentPoint(ParsedFractalTransition pft, ArrayStack<Complex> stack) {
        // set iteration to 0
        pft.getIterationVar().setValue(Complex.ZERO);
        
        // compute base
        pft.getRecurrenceVar().setValue(Interpreter.interpret(pft.getBase(), stack));
        
        // iterate while not halted by halt expression
        for(int i=1; !Interpreter.interpret(pft.getHalt(), stack).getBoolean(); i++) {
            // update iteration 
            pft.getIterationVar().setValue(new Complex(i));
                    
            // compute recurrence
            pft.getRecurrenceVar().setValue(Interpreter.interpret(pft.getRecurrence(), stack));
        }         

        // compute rgb
        int r = Math.abs(Interpreter.interpret(pft.getRed(), stack).getInt()) % 256;
        int g = Math.abs(Interpreter.interpret(pft.getGreen(), stack).getInt()) % 256;
        int b = Math.abs(Interpreter.interpret(pft.getBlue(), stack).getInt()) % 256;

        // return result  
        return new Color(r, g, b);        
    }    
    
    private static BufferedImage createFractalImage(ParsedFractal pf, int imgWidth, int imgHeight) {
        BufferedImage image = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_ARGB);
        Graphics g = image.getGraphics();        
        
        double reLower = pf.getRegionX();
        double imLower = pf.getRegionY();
        double reInterval = pf.getRegionWidth() / imgWidth;
        double imInterval = pf.getRegionHeight() / imgHeight;
        
        for(int x=0; x<imgWidth; x++) {
            for(int y=0; y<imgHeight; y++) {
                pf.getPointVar().setValue(new Complex(reLower + x * reInterval, imLower + y * imInterval));
                g.setColor(computeCurrentPoint(pf));
                g.fillRect(x, y, 1, 1);
            }
        }
        
        return image;
    }
    
    private static void createFractalImages(FractalTransition ft, ParsedFractalTransition pft, TransitionListener listener, int frames, int imgWidth, int imgHeight) {             
        ArrayStack<Complex> stack = new ArrayStack<Complex>(maxStack(pft));
        
        pft.getFramesVar().setValue(new Complex(frames));
        
        for(int i=0; i<frames; i++) {
            BufferedImage image = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_ARGB);
            Graphics g = image.getGraphics();
            
            pft.getFrameVar().setValue(new Complex(i));
            
            double reLower = Interpreter.interpret(pft.getRegionX(), stack).getReal();
            double imLower = Interpreter.interpret(pft.getRegionY(), stack).getReal();
            double reInterval = Interpreter.interpret(pft.getRegionWidth(), stack).getReal() / imgWidth;
            double imInterval = Interpreter.interpret(pft.getRegionHeight(), stack).getReal() / imgHeight;
            
            for(int x=0; x<imgWidth; x++) {
                for(int y=0; y<imgHeight; y++) {
                    pft.getPointVar().setValue(new Complex(reLower + x * reInterval, imLower + y * imInterval));
                    g.setColor(computeCurrentPoint(pft, stack));
                    g.fillRect(x, y, 1, 1);
                }
            }
            
            listener.transition(ft, image, i, frames);
        }
        
    }    
    
}
